package de.mmis.core.base;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;

/**
 * provides global annotations and utility methods using them
 * 
 * @author Christian Koch
 * 
 */
public class Annotations {
	private static Logger LOGGER = Logger.getLogger(Annotations.class);

	@Inherited
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public static @interface NoProperty {
		// empty
	}

	public enum TimeScale {
		MICROSECONDS, MILLISECONDS, SECONDS, MINUTES
	}

	/**
	 * annotation setting the default timeout for calls of methods of this
	 * class. May be overridden by any {@link MethodTimeoutAnnotation} or
	 * {@link ParameterTimeoutAnnotation}
	 * 
	 * @author Christian Koch
	 * 
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public static @interface ClassTimeoutAnnotation {
		long timeoutMillis();
	}

	/**
	 * annotation setting the default timeout for calls of this method.
	 * Overrides any {@link ClassTimeoutAnnotation} and may be overridden by any
	 * {@link ParameterTimeoutAnnotation}
	 * 
	 * @author Christian Koch
	 * 
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public static @interface MethodTimeoutAnnotation {
		long timeoutMillis();
	}

	/**
	 * annotation stating that this parameter indicates the timeout for calls of
	 * this method. Overrides any {@link ClassTimeoutAnnotation} and
	 * {@link MethodTimeoutAnnotation}
	 * 
	 * @author Christian Koch
	 * 
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.PARAMETER)
	public static @interface ParameterTimeoutAnnotation {
		TimeScale timescale();
	}

	/**
	 * determines the timeout defined by the timeout annotations of this method
	 * 
	 * @param method
	 *            method to determine timeout for
	 * @param args
	 *            arguments of the method call. Used by
	 *            {@link ParameterTimeoutAnnotation}. If set to
	 *            <code>null</code>, {@link ParameterTimeoutAnnotation}s will be
	 *            ignored.
	 * @param defaultTimeout
	 *            default timeout used if no annotation found and as an offest
	 *            added to the {@link ParameterTimeoutAnnotation} value.
	 * @return the resulting timeout for this method call.
	 */
	public static long getAnnotationTimeout(Method method, Object[] args,
			long defaultTimeout) {
		long timeout = defaultTimeout;
		// get class timeout annotation
		ClassTimeoutAnnotation ct = method.getDeclaringClass().getAnnotation(
				ClassTimeoutAnnotation.class);
		if (ct != null)
			timeout = ct.timeoutMillis();
		// get method timeout annotation
		MethodTimeoutAnnotation mt = method
				.getAnnotation(MethodTimeoutAnnotation.class);
		if (mt != null)
			timeout = mt.timeoutMillis();
		// get parameter timeout annotation
		for (int i = 0; i < method.getParameterTypes().length; i++)
			for (Annotation annotation : method.getParameterAnnotations()[i])
				if (annotation instanceof ParameterTimeoutAnnotation) {
					if (args[i] instanceof Long)
						timeout = ((Long) args[i]).longValue();
					else if (args[i] instanceof Integer)
						timeout = ((Integer) args[i]).longValue();
					else {
						LOGGER.warn("timeout annotation found on method "
								+ method
								+ " parameter "
								+ i
								+ " is neither an int nor a long and therefore cannot be used as timeout parameter");
						continue;
					}
					switch (((ParameterTimeoutAnnotation) annotation)
							.timescale()) {
					case MICROSECONDS:
						timeout /= 1000;
						break;
					case MILLISECONDS:
						break;
					case SECONDS:
						timeout *= 1000;
						break;
					case MINUTES:
						timeout *= 60000;
						break;
					}
					timeout += defaultTimeout;
				}
		return timeout;
	}
}